/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     iFreeOS TCP/IP protocol driver
 * FILE:        datalink/lan.c
 * PURPOSE:     Local Area Network media routines
 * PROGRAMMERS: Casper S. Hornstrup (chorns@users.sourceforge.net)
 * REVISIONS:
 *   CSH 01/08-2000 Created
 *   arty -- Separate service 09/2004
 */

#include "precomp.h"

ULONG DebugTraceLevel = 0x7ffffff;
PDEVICE_OBJECT LanDeviceObject  = NULL;

/*!
 * @brief Send a request to NDIS
 *
 * @param Adapter     = Pointer to a LAN_ADAPTER structure
 * @param Type        = Type of request (Set or Query)
 * @param OID         = Value to be set/queried for
 * @param Buffer      = Pointer to a buffer to use
 * @param Length      = Number of bytes in Buffer
 *
 * @return Status of operation
 */
NDIS_STATUS
NDISCall(
    PLAN_ADAPTER Adapter,
    NDIS_REQUEST_TYPE Type,
    NDIS_OID OID,
    PVOID Buffer,
    UINT Length)
{
    NDIS_REQUEST Request;
    NDIS_STATUS NdisStatus;

    Request.RequestType = Type;
    if (Type == NdisRequestSetInformation)
    {
        Request.DATA.SET_INFORMATION.Oid = OID;
        Request.DATA.SET_INFORMATION.InformationBuffer = Buffer;
        Request.DATA.SET_INFORMATION.InformationBufferLength = Length;
    }
    else
    {
        Request.DATA.QUERY_INFORMATION.Oid = OID;
        Request.DATA.QUERY_INFORMATION.InformationBuffer = Buffer;
        Request.DATA.QUERY_INFORMATION.InformationBufferLength = Length;
    }

    if (Adapter->State != LAN_STATE_RESETTING)
    {
        NdisRequest(&NdisStatus, Adapter->NdisHandle, &Request);
    }
    else
    {
        NdisStatus = NDIS_STATUS_NOT_ACCEPTED;
    }

    /* Wait for NDIS to complete the request */
    if (NdisStatus == NDIS_STATUS_PENDING)
    {
        KeWaitForSingleObject(&Adapter->Event,
                              UserRequest,
                              KernelMode,
                              FALSE,
                              NULL);
        NdisStatus = Adapter->NdisStatus;
    }

    return NdisStatus;
}


/*!
 * @brief Frees memory for a LAN_ADAPTER structure
 *
 * @param Adapter = Pointer to LAN_ADAPTER structure to free
 */
VOID
FreeAdapter(
    PLAN_ADAPTER Adapter)
{
    exFreePool(Adapter);
}


/*!
 * @brief Called by NDIS to complete opening of an adapter
 *
 * @param BindingContext  = Pointer to a device context (LAN_ADAPTER)
 * @param Status          = Status of the operation
 * @param OpenErrorStatus = Additional status information
 */
VOID
NTAPI
ProtocolOpenAdapterComplete(
    NDIS_HANDLE BindingContext,
    NDIS_STATUS Status,
    NDIS_STATUS OpenErrorStatus)
{
    PLAN_ADAPTER Adapter = (PLAN_ADAPTER)BindingContext;

    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));

    KeSetEvent(&Adapter->Event, 0, FALSE);
}


/*!
 * @brief Called by NDIS to complete closing an adapter
 *
 * @param BindingContext = Pointer to a device context (LAN_ADAPTER)
 * @param Status         = Status of the operation
 */
VOID
NTAPI
ProtocolCloseAdapterComplete(
    NDIS_HANDLE BindingContext,
    NDIS_STATUS Status)
{
    PLAN_ADAPTER Adapter = (PLAN_ADAPTER)BindingContext;

    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));

    Adapter->NdisStatus = Status;

    KeSetEvent(&Adapter->Event, 0, FALSE);
}


/*!
 * @brief Called by NDIS to complete resetting an adapter
 *
 * @param BindingContext = Pointer to a device context (LAN_ADAPTER)
 * @param Status         = Status of the operation
 */
VOID
NTAPI
ProtocolResetComplete(
    NDIS_HANDLE BindingContext,
    NDIS_STATUS Status)
{
    LA_DbgPrint(MID_TRACE, ("Called.\n"));
}


/*!
 * @brief Called by NDIS to complete a request
 *
 * @param BindingContext = Pointer to a device context (LAN_ADAPTER)
 * @param NdisRequest    = Pointer to an object describing the request
 * @param Status         = Status of the operation
 */
VOID
NTAPI
ProtocolRequestComplete(
    NDIS_HANDLE BindingContext,
    PNDIS_REQUEST NdisRequest,
    NDIS_STATUS Status)
{
    PLAN_ADAPTER Adapter = (PLAN_ADAPTER)BindingContext;

    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));

    /* Save status of request and signal an event */
    Adapter->NdisStatus = Status;

    KeSetEvent(&Adapter->Event, 0, FALSE);
}


/*!
 * @brief Called by NDIS to complete sending process
 *
 * @param BindingContext = Pointer to a device context (LAN_ADAPTER)
 * @param Packet         = Pointer to a packet descriptor
 * @param Status         = Status of the operation
 */
VOID
NTAPI
ProtocolSendComplete(
    NDIS_HANDLE BindingContext,
    PNDIS_PACKET Packet,
    NDIS_STATUS Status)
{
    /*PLAN_ADAPTER Adapter = (PLAN_ADAPTER)BindingContext;*/

    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));
    /*(*PC(Packet)->DLComplete)(Adapter->Context, Packet, Status);*/
    LA_DbgPrint(DEBUG_DATALINK, ("Finished\n"));
}


/*!
 * @brief Called by NDIS to complete reception of data
 *
 * @param BindingContext   = Pointer to a device context (LAN_ADAPTER)
 * @param Packet           = Pointer to a packet descriptor
 * @param Status           = Status of the operation
 * @param BytesTransferred = Number of bytes transferred
 *
 * @note If the packet was successfully received, determine the protocol
 * type and pass it to the correct receive handler
 */
VOID
NTAPI
ProtocolTransferDataComplete(
    NDIS_HANDLE BindingContext,
    PNDIS_PACKET Packet,
    NDIS_STATUS Status,
    UINT BytesTransferred)
{
    PLIST_ENTRY ListEntry, ReadListEntry;
    PLAN_PROTOCOL Proto;
    PLAN_PACKET_HEADER Header;
    PLAN_DEVICE_EXT DeviceExt = LanDeviceObject->DeviceExtension;
    UINT i;
    UINT PacketType;
    UINT ContigSize;
    PIRP ReadIrp;
    KIRQL OldIrql;
    LAN_PACKET LPPacket;
    PLAN_ADAPTER Adapter = (PLAN_ADAPTER)BindingContext;

    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));

    KeAcquireSpinLock(&DeviceExt->Lock, &OldIrql);

    if (Status == NDIS_STATUS_SUCCESS)
    {
        PNDIS_BUFFER NdisBuffer;

        NdisGetFirstBufferFromPacket(Packet,
                                     &NdisBuffer,
                                     &LPPacket.EthHeader,
                                     &ContigSize,
                                     &LPPacket.TotalSize);

        LPPacket.TotalSize = BytesTransferred;

        /* Determine which upper layer protocol that should receive
           this packet and pass it to the correct receive handler */

        /*OskitDumpBuffer(IPPacket.Header, BytesTransferred);*/

        PacketType = LPPacket.EthHeader->EType;

        LA_DbgPrint(DEBUG_DATALINK,
                    ("Ether Type = %x Total = %d Packet %x Payload %x\n",
                     PacketType, LPPacket.TotalSize, LPPacket.EthHeader,
                     LPPacket.EthHeader + 1));

        NdisBuffer->Next = NULL;

        for (ListEntry = DeviceExt->ProtocolListHead.Flink;
             ListEntry != &DeviceExt->ProtocolListHead;
             ListEntry = ListEntry->Flink)
        {
            Proto = CONTAINING_RECORD(ListEntry, LAN_PROTOCOL, ListEntry);
            LA_DbgPrint(MID_TRACE,("Examining protocol %x\n", Proto));

            for (i = 0; i < Proto->NumEtherTypes; i++)
            {
                LA_DbgPrint(MID_TRACE,(".Accepts proto %x\n",
                                       Proto->EtherType[i]));

                if (Proto->EtherType[i] == PacketType &&
                    !IsListEmpty(&Proto->ReadIrpListHead))
                {
                    ReadListEntry = RemoveHeadList(&Proto->ReadIrpListHead);
                    ReadIrp = CONTAINING_RECORD(ReadListEntry,
                                                IRP,
                                                Tail.Overlay.ListEntry);
                    LA_DbgPrint(MID_TRACE,("..Irp %x\n", ReadIrp));

                    _SEH2_TRY
                    {
                        Header = ReadIrp->AssociatedIrp.SystemBuffer;
                        LA_DbgPrint(MID_TRACE,
                                    ("Writing packet at %x\n", Header));

                        Header->Fixed.Adapter = Adapter->Index;
                        Header->Fixed.AddressType = Adapter->Media;
                        Header->Fixed.AddressLen = IEEE_802_ADDR_LENGTH;
                        Header->Fixed.PacketType = PacketType;

                        RtlCopyMemory(Header->Address,
                                      LPPacket.EthHeader->SrcAddr,
                                      IEEE_802_ADDR_LENGTH);

                        if (Proto->Buffered)
                        {
                            LA_DbgPrint(MID_TRACE,("Buffered copy\n"));
                            RtlCopyMemory(Header->Address + IEEE_802_ADDR_LENGTH,
                                          LPPacket.EthHeader + 1,
                                          LPPacket.TotalSize -
                                            sizeof(*LPPacket.EthHeader) );
                            Header->Fixed.Mdl = NULL;
                        }
                        else
                            Header->Fixed.Mdl = NdisBuffer;

                        ReadIrp->IoStatus.Status = 0;
                        ReadIrp->IoStatus.Information =
                            (Header->Address + IEEE_802_ADDR_LENGTH +
                             LPPacket.TotalSize -
                             sizeof(*LPPacket.EthHeader)) -
                            (PCHAR)Header;

                        LA_DbgPrint(MID_TRACE,("Bytes returned %d\n",
                        ReadIrp->IoStatus.Information));

                        #error move this out of SEH!
                        IoCompleteRequest(ReadIrp, IO_NETWORK_INCREMENT);
                    }
                    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
                    {
                        LA_DbgPrint(MIN_TRACE,
                                    ("Failed write to packet in client\n"));
                        ReadIrp->IoStatus.Status = STATUS_ACCESS_VIOLATION;
                        ReadIrp->IoStatus.Information = 0;
                        IoCompleteRequest(ReadIrp, IO_NETWORK_INCREMENT);
                    }
                    _SEH2_END;
                    break;
                }
            }
        }
    }

    KeReleaseSpinLock(&DeviceExt->Lock, OldIrql);

    FreeNdisPacket(Packet);
}


/*!
 * @brief Called by NDIS when a packet has been received on the physical link
 *
 * @param BindingContext      = Pointer to a device context (LAN_ADAPTER)
 * @param MacReceiveContext   = Handle used by underlying NIC driver
 * @param HeaderBuffer        = Pointer to a buffer containing the packet header
 * @param HeaderBufferSize    = Number of bytes in HeaderBuffer
 * @param LookaheadBuffer     = Pointer to a buffer containing buffered packet data
 * @param LookaheadBufferSize = Size of LookaheadBuffer. May be less than asked for
 * @param PacketSize          = Overall size of the packet (not including header)
 *
 * @return Status of operation
 */
NDIS_STATUS
NTAPI
ProtocolReceive(
    NDIS_HANDLE BindingContext,
    NDIS_HANDLE MacReceiveContext,
    PVOID HeaderBuffer,
    UINT HeaderBufferSize,
    PVOID LookaheadBuffer,
    UINT LookaheadBufferSize,
    UINT PacketSize)
{
    USHORT EType;
    UINT PacketType, BytesTransferred;
    PCHAR BufferData;
    NDIS_STATUS NdisStatus;
    PNDIS_PACKET NdisPacket;
    PLAN_ADAPTER Adapter = (PLAN_ADAPTER)BindingContext;

    LA_DbgPrint(DEBUG_DATALINK, ("Called. (packetsize %d)\n",PacketSize));

    if (Adapter->State != LAN_STATE_STARTED)
    {
        LA_DbgPrint(DEBUG_DATALINK, ("Adapter is stopped.\n"));
        return NDIS_STATUS_NOT_ACCEPTED;
    }

    if (HeaderBufferSize < Adapter->HeaderSize)
    {
        LA_DbgPrint(DEBUG_DATALINK, ("Runt frame received.\n"));
        return NDIS_STATUS_NOT_ACCEPTED;
    }

    PacketType = EType;

    /* Get a transfer data packet */
    KeAcquireSpinLockAtDpcLevel(&Adapter->Lock);
    NdisStatus = AllocatePacketWithBuffer(&NdisPacket, NULL, Adapter->MTU);
    if (NdisStatus != NDIS_STATUS_SUCCESS)
    {
        KeReleaseSpinLockFromDpcLevel(&Adapter->Lock);
        return NDIS_STATUS_NOT_ACCEPTED;
    }

    LA_DbgPrint(DEBUG_DATALINK,
                ("pretransfer LookaheadBufferSize %d packsize %d\n",
                 LookaheadBufferSize,PacketSize));

    {
        UINT temp;
        temp = PacketSize;
        GetDataPtr(NdisPacket, 0, &BufferData, &temp);
    }

    LA_DbgPrint(DEBUG_DATALINK,
                ("pretransfer LookaheadBufferSize %d HeaderBufferSize %d packsize %d\n",
                 LookaheadBufferSize,HeaderBufferSize,PacketSize));

    /* Get the data */
    NdisTransferData(&NdisStatus,
                     Adapter->NdisHandle,
                     MacReceiveContext,
                     0,
                     PacketSize + HeaderBufferSize,
                     NdisPacket,
                     &BytesTransferred);

    LA_DbgPrint(DEBUG_DATALINK, ("Calling complete\n"));

    if (NdisStatus != NDIS_STATUS_PENDING)
    {
        ProtocolTransferDataComplete(BindingContext,
                                     NdisPacket,
                                     NdisStatus,
                                     PacketSize + HeaderBufferSize);
    }

    /* Release the packet descriptor */
    KeReleaseSpinLockFromDpcLevel(&Adapter->Lock);
    LA_DbgPrint(DEBUG_DATALINK, ("leaving\n"));

    return NDIS_STATUS_SUCCESS;
}


/*!
 * @brief Called by NDIS when we're done receiving data
 *
 * @param BindingContext = Pointer to a device context (LAN_ADAPTER)
 */
VOID
NTAPI
ProtocolReceiveComplete(
    NDIS_HANDLE BindingContext)
{
    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));
}


/*!
 * @brief Called by NDIS when the underlying driver has changed state
 *
 * @param BindingContext   = Pointer to a device context (LAN_ADAPTER)
 * @param GenerelStatus    = A generel status code
 * @param StatusBuffer     = Pointer to a buffer with medium-specific data
 * @param StatusBufferSize = Number of bytes in StatusBuffer
 */
VOID
NTAPI
ProtocolStatus(
    NDIS_HANDLE BindingContext,
    NDIS_STATUS GenerelStatus,
    PVOID StatusBuffer,
    UINT StatusBufferSize)
{
    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));
}


/*!
 * @brief Called by NDIS when a status-change has occurred
 *
 * @param BindingContext = Pointer to a device context (LAN_ADAPTER)
 */
VOID
NTAPI
ProtocolStatusComplete(
    NDIS_HANDLE NdisBindingContext)
{
    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));
}

/*!
 * @brief Called by NDIS during NdisRegisterProtocol to set up initial
 *           bindings, and periodically thereafer as new adapters come online
 *
 * @param Status - Return value to NDIS
 * @param BindContext - Handle provided by NDIS to track pending binding operations
 * @param DeviceName - Name of the miniport device to bind to
 * @param SystemSpecific1 - Pointer to a registry path with protocol-specific
 *        configuration information
 * @param SystemSpecific2 - Unused & must not be touched
 */
VOID
NTAPI
ProtocolBindAdapter(
    OUT PNDIS_STATUS   Status,
    IN  NDIS_HANDLE    BindContext,
    IN  PNDIS_STRING   DeviceName,
    IN  PVOID          SystemSpecific1,
    IN  PVOID          SystemSpecific2)
{
    /* XXX confirm that this is still true, or re-word the following comment */
    /* we get to ignore BindContext because we will never pend an operation with NDIS */
    LA_DbgPrint(DEBUG_DATALINK, ("Called with registry path %wZ\n", SystemSpecific1));
    *Status = LANRegisterAdapter(DeviceName, SystemSpecific1);
}


/*!
 * @brief Transmits a packet
 * ARGUMENTS:
 * @param Context     = Pointer to context information (LAN_ADAPTER)
 * @param NdisPacket  = Pointer to NDIS packet to send
 * @param LinkAddress = Pointer to link address of destination (NULL = broadcast)
 * @param Type        = LAN protocol type (LAN_PROTO_*)
 */
VOID
LANTransmit(
    PLAN_ADAPTER Adapter,
    PNDIS_PACKET NdisPacket,
    PVOID LinkAddress,
    USHORT Type)
{
    NDIS_STATUS NdisStatus;

    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));

    if (Adapter->State == LAN_STATE_STARTED)
    {
        NdisSend(&NdisStatus, Adapter->NdisHandle, NdisPacket);
        if (NdisStatus != NDIS_STATUS_PENDING)
            ProtocolSendComplete((NDIS_HANDLE)Adapter, NdisPacket, NdisStatus);
    }
    else
    {
        ProtocolSendComplete((NDIS_HANDLE)Adapter, NdisPacket, NDIS_STATUS_CLOSED);
    }
}

/* For use internally */
UINT
LANTransmitInternal(PLAN_PACKET_HEADER ToWrite, UINT OverallLength)
{
    NDIS_STATUS NdisStatus;
    PLAN_DEVICE_EXT DeviceExt = LanDeviceObject->DeviceExtension;
    PLAN_ADAPTER Adapter;
    PETH_HEADER EthHeader;
    KIRQL OldIrql;
    PNDIS_PACKET NdisPacket;
    UINT Size, PayloadSize = OverallLength -
                             ((ToWrite->Address + ToWrite->Fixed.AddressLen) - (PCHAR)ToWrite);

    NdisStatus = AllocatePacketWithBuffer(&NdisPacket, NULL,
                                           PayloadSize + sizeof(ETH_HEADER));

    KeAcquireSpinLock(&DeviceExt->Lock, &OldIrql);

    if (!NT_SUCCESS(NdisStatus)) goto end;

    Adapter = FindAdapterByIndex(DeviceExt, ToWrite->Fixed.Adapter);

    if (!Adapter) goto end;

    GetDataPtr(NdisPacket, 0, (PCHAR *)&EthHeader, &Size);
    if (!EthHeader) goto end;

    LA_DbgPrint(MID_TRACE,("Writing %d bytes of Dst\n",
                           ToWrite->Fixed.AddressLen));

    /* Handle broadcast for other media types here */
    if (ToWrite->Fixed.AddressLen)
    {
        RtlCopyMemory(EthHeader->DstAddr,
                      ToWrite->Address,
                      ToWrite->Fixed.AddressLen);
    }
    else
        memset(EthHeader->DstAddr, -1, sizeof(EthHeader->DstAddr));

    LA_DbgPrint(MID_TRACE,
                ("Writing %d bytes of Src\n", Adapter->HWAddressLength));

    RtlCopyMemory(EthHeader->SrcAddr,
                  Adapter->HWAddress,
                  Adapter->HWAddressLength);

    LA_DbgPrint(MID_TRACE,
                ("Writing %d bytes of payload\n", PayloadSize));

    EthHeader->EType = ToWrite->Fixed.PacketType;
    RtlCopyMemory(EthHeader + 1,
                  ToWrite->Address + ToWrite->Fixed.AddressLen,
                  PayloadSize);

    LANTransmit(Adapter,
                NdisPacket,
                ToWrite->Address,
                ToWrite->Fixed.PacketType);

end:
    KeReleaseSpinLock(&DeviceExt->Lock, OldIrql);

    return OverallLength;
}

/*!
 * @brief Binds a LAN adapter to IP layer
 *
 * @param Adapter = Pointer to LAN_ADAPTER structure
 *
 * @note We set the lookahead buffer size, set the packet filter and
 * bind the adapter to IP layer
 */
VOID
BindAdapter(PLAN_ADAPTER Adapter, PNDIS_STRING RegistryPath)
{
    /*NDIS_STATUS NdisStatus;*/
    /*ULONG Lookahead = LOOKAHEAD_SIZE;*/
    /*NTSTATUS Status;*/
    /*HANDLE RegHandle = 0;*/

    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));

}

/*!
 * @brief Registers protocol with an NDIS adapter
 *
 * @param AdapterName = Pointer to string with name of adapter to register
 * @param Adapter     = Address of pointer to a LAN_ADAPTER structure
 *
 * @return Status of operation
 */
NDIS_STATUS
LANRegisterAdapter(
    PNDIS_STRING AdapterName,
    PNDIS_STRING RegistryPath)
{
    PLAN_ADAPTER Adapter;
    NDIS_MEDIUM MediaArray[MAX_MEDIA];
    NDIS_STATUS NdisStatus;
    NDIS_STATUS OpenStatus;
    UINT MediaIndex;
    UINT AddressOID;
    UINT Speed;
    PLAN_DEVICE_EXT DeviceExt = LanDeviceObject->DeviceExtension;

    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));

    Adapter = exAllocatePool(NonPagedPool, sizeof(LAN_ADAPTER));
    if (!Adapter)
    {
        LA_DbgPrint(MIN_TRACE, ("Insufficient resources.\n"));
        return NDIS_STATUS_RESOURCES;
    }

    RtlZeroMemory(Adapter, sizeof(LAN_ADAPTER));

    /* Put adapter in stopped state */
    Adapter->State = LAN_STATE_STOPPED;
    Adapter->Index = DeviceExt->AdapterId++;

    InitializeListHead(&Adapter->AddressList);
    InitializeListHead(&Adapter->ForeignList);

    /* Initialize protecting spin lock */
    KeInitializeSpinLock(&Adapter->Lock);

    KeInitializeEvent(&Adapter->Event, SynchronizationEvent, FALSE);

    /* Initialize array with media IDs we support */
    MediaArray[MEDIA_ETH] = NdisMedium802_3;

    LA_DbgPrint(DEBUG_DATALINK,("opening adapter %wZ\n", AdapterName));

    /* Open the adapter. */
    NdisOpenAdapter(&NdisStatus,
                    &OpenStatus,
                    &Adapter->NdisHandle,
                    &MediaIndex,
                    MediaArray,
                    MAX_MEDIA,
                    DeviceExt->NdisProtocolHandle,
                    Adapter,
                    AdapterName,
                    0,
                    NULL);

    /* Wait until the adapter is opened */
    if (NdisStatus == NDIS_STATUS_PENDING)
        KeWaitForSingleObject(&Adapter->Event, UserRequest, KernelMode, FALSE, NULL);
    else if (NdisStatus != NDIS_STATUS_SUCCESS)
    {
        exFreePool(Adapter);
        return NdisStatus;
    }

    Adapter->Media = MediaArray[MediaIndex];

    /* Fill LAN_ADAPTER structure with some adapter specific information */
    switch (Adapter->Media)
    {
    case NdisMedium802_3:
        Adapter->HWAddressLength = IEEE_802_ADDR_LENGTH;
        Adapter->BCastMask       = BCAST_ETH_MASK;
        Adapter->BCastCheck      = BCAST_ETH_CHECK;
        Adapter->BCastOffset     = BCAST_ETH_OFFSET;
        Adapter->HeaderSize      = sizeof(ETH_HEADER);
        Adapter->MinFrameSize    = 60;
        AddressOID          = OID_802_3_CURRENT_ADDRESS;
        Adapter->PacketFilter    =
            NDIS_PACKET_TYPE_BROADCAST |
            NDIS_PACKET_TYPE_DIRECTED  |
            NDIS_PACKET_TYPE_MULTICAST;
        break;

    default:
        /* Unsupported media */
        LA_DbgPrint(MIN_TRACE, ("Unsupported media.\n"));
        exFreePool(Adapter);
        return NDIS_STATUS_NOT_SUPPORTED;
    }

    /* Get maximum frame size */
    NdisStatus = NDISCall(Adapter,
                          NdisRequestQueryInformation,
                          OID_GEN_MAXIMUM_FRAME_SIZE,
                          &Adapter->MTU,
                          sizeof(UINT));
    if (NdisStatus != NDIS_STATUS_SUCCESS)
    {
        exFreePool(Adapter);
        return NdisStatus;
    }

    /* Get maximum packet size */
    NdisStatus = NDISCall(Adapter,
                          NdisRequestQueryInformation,
                          OID_GEN_MAXIMUM_TOTAL_SIZE,
                          &Adapter->MaxPacketSize,
                          sizeof(UINT));
    if (NdisStatus != NDIS_STATUS_SUCCESS)
    {
        LA_DbgPrint(MIN_TRACE, ("Query for maximum packet size failed.\n"));
        exFreePool(Adapter);
        return NdisStatus;
    }

    /* Get maximum number of packets we can pass to NdisSend(Packets) at one time */
    NdisStatus = NDISCall(Adapter,
                          NdisRequestQueryInformation,
                          OID_GEN_MAXIMUM_SEND_PACKETS,
                          &Adapter->MaxSendPackets,
                          sizeof(UINT));
    if (NdisStatus != NDIS_STATUS_SUCCESS)
    {
        /* Legacy NIC drivers may not support this query, if it fails we
           assume it can send at least one packet per call to NdisSend(Packets) */
        Adapter->MaxSendPackets = 1;
    }

    /* Get current hardware address */
    NdisStatus = NDISCall(Adapter,
                          NdisRequestQueryInformation,
                          AddressOID,
                          Adapter->HWAddress,
                          Adapter->HWAddressLength);
    if (NdisStatus != NDIS_STATUS_SUCCESS)
    {
        LA_DbgPrint(MIN_TRACE, ("Query for current hardware address failed.\n"));
        exFreePool(Adapter);
        return NdisStatus;
    }

    /* Get maximum link speed */
    NdisStatus = NDISCall(Adapter,
                          NdisRequestQueryInformation,
                          OID_GEN_LINK_SPEED,
                          &Speed,
                          sizeof(UINT));
    if (NdisStatus != NDIS_STATUS_SUCCESS)
    {
        LA_DbgPrint(MIN_TRACE, ("Query for maximum link speed failed.\n"));
        exFreePool(Adapter);
        return NdisStatus;
    }

    /* Convert returned link speed to bps (it is in 100bps increments) */
    Adapter->Speed = Speed * 100L;

    /* Add adapter to the adapter list */
    ExInterlockedInsertTailList(&DeviceExt->AdapterListHead,
                                &Adapter->ListEntry,
                                &DeviceExt->Lock);

    Adapter->RegistryPath.Buffer =
        ExAllocatePool(NonPagedPool, RegistryPath->MaximumLength);
    if (!Adapter->RegistryPath.Buffer)
        return NDIS_STATUS_RESOURCES;

    RtlCopyUnicodeString(&Adapter->RegistryPath, RegistryPath);

    NdisStatus = NDISCall(Adapter,
                          NdisRequestSetInformation,
                          OID_GEN_CURRENT_LOOKAHEAD,
                          &Adapter->Lookahead,
                          sizeof(ULONG));
    if (NdisStatus != NDIS_STATUS_SUCCESS)
    {
        LA_DbgPrint(MID_TRACE,
                    ("Could not set lookahead buffer size (0x%X).\n",
                     NdisStatus));
        return NdisStatus;
    }

    /* Set packet filter so we can send and receive packets */
    NdisStatus = NDISCall(Adapter,
                          NdisRequestSetInformation,
                          OID_GEN_CURRENT_PACKET_FILTER,
                          &Adapter->PacketFilter,
                          sizeof(UINT));
    if (NdisStatus != NDIS_STATUS_SUCCESS)
    {
        LA_DbgPrint(MID_TRACE, ("Could not set packet filter (0x%X).\n",
                                NdisStatus));
        return NdisStatus;
    }

    Adapter->State = LAN_STATE_STARTED;

    LA_DbgPrint(DEBUG_DATALINK, ("Leaving.\n"));

    return NDIS_STATUS_SUCCESS;
}


/*!
 * @brief Unregisters protocol with NDIS adapter
 *
 * @param Adapter = Pointer to a LAN_ADAPTER structure
 *
 * @return Status of operation
 */
NDIS_STATUS
LANUnregisterAdapter(
    PLAN_ADAPTER Adapter)
{
    KIRQL OldIrql;
    NDIS_HANDLE NdisHandle;
    NDIS_STATUS NdisStatus = NDIS_STATUS_SUCCESS;

    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));

    /* Unlink the adapter from the list */
    RemoveEntryList(&Adapter->ListEntry);

    KeAcquireSpinLock(&Adapter->Lock, &OldIrql);
    NdisHandle = Adapter->NdisHandle;
    if (NdisHandle)
    {
        Adapter->NdisHandle = NULL;
        KeReleaseSpinLock(&Adapter->Lock, OldIrql);

        NdisCloseAdapter(&NdisStatus, NdisHandle);
        if (NdisStatus == NDIS_STATUS_PENDING)
        {
            KeWaitForSingleObject(&Adapter->Event,
                                  UserRequest,
                                  KernelMode,
                                  FALSE,
                                  NULL);
            NdisStatus = Adapter->NdisStatus;
        }
    }
    else
        KeReleaseSpinLock(&Adapter->Lock, OldIrql);

    FreeAdapter(Adapter);

    return NDIS_STATUS_SUCCESS;
}

/*!
 * @brief Registers this protocol driver with NDIS
 *
 * @param Name = Name of this protocol driver
 *
 * @return Status of operation
 */
NTSTATUS
LANRegisterProtocol(PNDIS_STRING Name)
{
    NDIS_STATUS NdisStatus;
    NDIS_PROTOCOL_CHARACTERISTICS ProtChars;
    PLAN_DEVICE_EXT DeviceExt = LanDeviceObject->DeviceExtension;

    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));

    InitializeListHead(&DeviceExt->AdapterListHead);
    InitializeListHead(&DeviceExt->ProtocolListHead);

    /* Set up protocol characteristics */
    RtlZeroMemory(&ProtChars, sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
    ProtChars.MajorNdisVersion               = NDIS_VERSION_MAJOR;
    ProtChars.MinorNdisVersion               = NDIS_VERSION_MINOR;
    ProtChars.Name.Length                    = Name->Length;
    ProtChars.Name.Buffer                    = Name->Buffer;
    ProtChars.Name.MaximumLength             = Name->MaximumLength;
    ProtChars.OpenAdapterCompleteHandler     = ProtocolOpenAdapterComplete;
    ProtChars.CloseAdapterCompleteHandler    = ProtocolCloseAdapterComplete;
    ProtChars.ResetCompleteHandler           = ProtocolResetComplete;
    ProtChars.RequestCompleteHandler         = ProtocolRequestComplete;
    ProtChars.SendCompleteHandler            = ProtocolSendComplete;
    ProtChars.TransferDataCompleteHandler    = ProtocolTransferDataComplete;
    ProtChars.ReceiveHandler                 = ProtocolReceive;
    ProtChars.ReceiveCompleteHandler         = ProtocolReceiveComplete;
    ProtChars.StatusHandler                  = ProtocolStatus;
    ProtChars.StatusCompleteHandler          = ProtocolStatusComplete;
    ProtChars.BindAdapterHandler             = ProtocolBindAdapter;

    /* Try to register protocol */
    NdisRegisterProtocol(&NdisStatus,
                         &DeviceExt->NdisProtocolHandle,
                         &ProtChars,
                         sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
    if (NdisStatus != NDIS_STATUS_SUCCESS)
    {
        LA_DbgPrint(MID_TRACE, ("NdisRegisterProtocol failed, status 0x%x\n", NdisStatus));
        return (NTSTATUS)NdisStatus;
    }

    return STATUS_SUCCESS;
}


/*!
 * @brief Unregisters this protocol driver with NDIS
 * @note Does not care wether we are already registered
 */
VOID
LANUnregisterProtocol(VOID)
{
    PLAN_DEVICE_EXT DeviceExt = LanDeviceObject->DeviceExtension;

    LA_DbgPrint(DEBUG_DATALINK, ("Called.\n"));

    NDIS_STATUS NdisStatus;
    PLIST_ENTRY CurrentEntry;
    PLIST_ENTRY NextEntry;
    PLAN_ADAPTER Current;
    KIRQL OldIrql;

    KeAcquireSpinLock(&DeviceExt->Lock, &OldIrql);

    /* Search the list and remove every adapter we find */
    CurrentEntry = DeviceExt->AdapterListHead.Flink;
    while (CurrentEntry != &DeviceExt->AdapterListHead)
    {
        NextEntry = CurrentEntry->Flink;
        Current = CONTAINING_RECORD(CurrentEntry, LAN_ADAPTER, ListEntry);
        /* Unregister it */
        LANUnregisterAdapter(Current);
        CurrentEntry = NextEntry;
    }

    NdisDeregisterProtocol(&NdisStatus, DeviceExt->NdisProtocolHandle);
}

NTSTATUS
NTAPI
LanCreateProtocol(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PIO_STACK_LOCATION IrpSp)
{
    PLAN_PROTOCOL Proto;
    PFILE_FULL_EA_INFORMATION EaInfo;
    PLAN_DEVICE_EXT DeviceExt = (PLAN_DEVICE_EXT)DeviceObject->DeviceExtension;
    PFILE_OBJECT FileObject = IrpSp->FileObject;
    PCHAR ProtoNumbersToMatch;
    UINT Size = sizeof(*Proto);
    NTSTATUS Status = STATUS_SUCCESS;

    EaInfo = Irp->AssociatedIrp.SystemBuffer;
    Size += EaInfo->EaValueLength;
    Proto = ExAllocatePool(NonPagedPool, Size);

    if (!Proto)
    {
        Status = Irp->IoStatus.Status = STATUS_NO_MEMORY;
        IoCompleteRequest(Irp, IO_NETWORK_INCREMENT);
        return Status;
    }

    RtlZeroMemory(Proto, Size);

    Proto->Id = DeviceExt->ProtoId++;
    Proto->NumEtherTypes = EaInfo->EaValueLength / sizeof(USHORT);
    ProtoNumbersToMatch = EaInfo->EaName + EaInfo->EaNameLength + 1;

    LA_DbgPrint(MID_TRACE,("NumEtherTypes: %d\n", Proto->NumEtherTypes));

    RtlCopyMemory(Proto->EtherType,
                  ProtoNumbersToMatch,
                  sizeof(USHORT) * Proto->NumEtherTypes);

    InitializeListHead(&Proto->ReadIrpListHead);

    FileObject->FsContext = Proto;

    LA_DbgPrint(MID_TRACE,("DeviceExt: %x, Proto %x\n", DeviceExt, Proto));

    ExInterlockedInsertTailList(&DeviceExt->ProtocolListHead,
                                &Proto->ListEntry,
                                &DeviceExt->Lock);

    Irp->IoStatus.Information = 0;
    Irp->IoStatus.Status = STATUS_SUCCESS;

    LA_DbgPrint(MID_TRACE,("Status %x\n", Irp->IoStatus.Status));

    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}

NTSTATUS
NTAPI
LanCloseProtocol(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PIO_STACK_LOCATION IrpSp)
{
    PLAN_DEVICE_EXT DeviceExt = (PLAN_DEVICE_EXT)DeviceObject->DeviceExtension;
    PFILE_OBJECT FileObject = IrpSp->FileObject;
    PLAN_PROTOCOL Proto = FileObject->FsContext;
    KIRQL OldIrql;
    PLIST_ENTRY ReadIrpListEntry;
    PIRP ReadIrp;
    NTSTATUS Status;

    LA_DbgPrint(MID_TRACE,("Called\n"));

    KeAcquireSpinLock(&DeviceExt->Lock, &OldIrql);

    while (!IsListEmpty(&Proto->ReadIrpListHead))
    {
        ReadIrpListEntry = RemoveHeadList(&Proto->ReadIrpListHead);

        ReadIrp = CONTAINING_RECORD(ReadIrpListEntry,
                                    IRP,
                                    Tail.Overlay.ListEntry);
        ReadIrp->IoStatus.Information = 0;
        ReadIrp->IoStatus.Status = STATUS_UNSUCCESSFUL;
        IoCompleteRequest(ReadIrp, IO_NO_INCREMENT);
    }

    RemoveEntryList(&Proto->ListEntry);

    KeReleaseSpinLock(&DeviceExt->Lock, OldIrql);

    LA_DbgPrint(MID_TRACE,("Deleting %x\n"));

    ExFreePool(Proto);

    Status = Irp->IoStatus.Status = STATUS_SUCCESS;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return Status;
}

PLAN_ADAPTER
FindAdapterByIndex(
    PLAN_DEVICE_EXT DeviceExt,
    UINT Index)
{
    PLIST_ENTRY ListEntry;
    PLAN_ADAPTER Current, Target = NULL;

    for (ListEntry = DeviceExt->AdapterListHead.Flink;
         ListEntry != &DeviceExt->AdapterListHead;
         ListEntry = ListEntry->Flink)
    {
        Current = CONTAINING_RECORD(ListEntry, LAN_ADAPTER, ListEntry);
        if (Current->Index == Index)
        {
            Target = Current;
            break;
        }
    }

    return Target;
}

/* Write data to an adapter:
 * |<-              16               >| |<-- variable ... -->|
 * [indx] [addrtype] [addrlen ] [ptype] [packet-data ...]
 */
NTSTATUS
NTAPI
LanWriteData(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PIO_STACK_LOCATION IrpSp)
{
    PLAN_PACKET_HEADER ToWrite = Irp->AssociatedIrp.SystemBuffer;
    NTSTATUS Status = STATUS_SUCCESS;

    LA_DbgPrint(MID_TRACE,("Called\n"));

    Irp->IoStatus.Information =
        LANTransmitInternal(ToWrite, IrpSp->Parameters.Write.Length);
    Irp->IoStatus.Status = Status;

    IoCompleteRequest(Irp, IO_NETWORK_INCREMENT);
    return Status;
}

NTSTATUS
NTAPI
LanReadData(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PIO_STACK_LOCATION IrpSp)
{
    PLAN_DEVICE_EXT DeviceExt =
        (PLAN_DEVICE_EXT)DeviceObject->DeviceExtension;
    PFILE_OBJECT FileObject = IrpSp->FileObject;
    PLAN_PROTOCOL Proto = FileObject->FsContext;

    LA_DbgPrint(MID_TRACE,("Called on %x (%x)\n", Proto, Irp));

    ExInterlockedInsertTailList(&Proto->ReadIrpListHead,
                                &Irp->Tail.Overlay.ListEntry,
                                &DeviceExt->Lock);

    LA_DbgPrint(MID_TRACE,("List: %x %x\n",
                           Proto->ReadIrpListHead.Flink,
                           Irp->Tail.Overlay.ListEntry.Flink));

    IoMarkIrpPending(Irp);
    return STATUS_PENDING;
}

NTSTATUS
NTAPI
LanEnumAdapters(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PIO_STACK_LOCATION IrpSp)
{
    PLIST_ENTRY ListEntry;
    PLAN_DEVICE_EXT DeviceExt = (PLAN_DEVICE_EXT)DeviceObject->DeviceExtension;
    NTSTATUS Status = STATUS_SUCCESS;
    PLAN_ADAPTER Adapter;
    UINT AdapterCount = 0;
    PUINT Output = Irp->AssociatedIrp.SystemBuffer;
    KIRQL OldIrql;

    LA_DbgPrint(MID_TRACE,("Called\n"));

    KeAcquireSpinLock(&DeviceExt->Lock, &OldIrql);

    for (ListEntry = DeviceExt->AdapterListHead.Flink;
         ListEntry != &DeviceExt->AdapterListHead;
         ListEntry = ListEntry->Flink)
    {
        AdapterCount++;
    }

    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength >=
        AdapterCount * sizeof(UINT))
    {
        for (ListEntry = DeviceExt->AdapterListHead.Flink;
             ListEntry != &DeviceExt->AdapterListHead;
             ListEntry = ListEntry->Flink)
        {
            Adapter = CONTAINING_RECORD(ListEntry, LAN_ADAPTER, ListEntry);
            *Output++ = Adapter->Index;
        }
    }
    else
        Status = STATUS_BUFFER_TOO_SMALL;

    KeReleaseSpinLock(&DeviceExt->Lock, OldIrql);

    LA_DbgPrint(MID_TRACE,("Ending\n"));

    Irp->IoStatus.Status = Status;
    Irp->IoStatus.Information = (PCHAR)Output -
                                (PCHAR)Irp->AssociatedIrp.SystemBuffer;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return Status;
}

NTSTATUS
NTAPI
LanAdapterInfo(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PIO_STACK_LOCATION IrpSp)
{
    PLAN_DEVICE_EXT DeviceExt =
        (PLAN_DEVICE_EXT)DeviceObject->DeviceExtension;
    PLAN_ADAPTER Adapter;
    PLAN_ADDRESS_C Address;
    PUINT AdapterIndexPtr = Irp->AssociatedIrp.SystemBuffer;
    PLIST_ENTRY ListEntry;
    UINT BytesNeeded = sizeof(LAN_ADAPTER_INFO), AddrSize;
    NTSTATUS Status = STATUS_SUCCESS;
    PCHAR Writing = Irp->AssociatedIrp.SystemBuffer;
    PLAN_ADAPTER_INFO_S Info;
    KIRQL OldIrql;

    LA_DbgPrint(MID_TRACE,("Called\n"));

    KeAcquireSpinLock(&DeviceExt->Lock, &OldIrql);

    if (IrpSp->Parameters.DeviceIoControl.InputBufferLength <
            sizeof(*AdapterIndexPtr))
        Adapter = NULL;
    else
        Adapter = FindAdapterByIndex(DeviceExt, *AdapterIndexPtr);

    if (Adapter)
    {
        /* Local Addresses */
        for (ListEntry = Adapter->AddressList.Flink;
             ListEntry != &Adapter->AddressList;
             ListEntry = ListEntry->Flink)
        {
            Address = CONTAINING_RECORD(ListEntry, LAN_ADDRESS_C, ListEntry);
            BytesNeeded += LAN_ADDR_SIZE(Address->ClientPart.AddressLen,
                                         Address->ClientPart.HWAddressLen);
        }

        /* Foreign Addresses */
        for (ListEntry = Adapter->ForeignList.Flink;
             ListEntry != &Adapter->ForeignList;
             ListEntry = ListEntry->Flink)
        {
            Address = CONTAINING_RECORD(ListEntry, LAN_ADDRESS_C, ListEntry);
            BytesNeeded += LAN_ADDR_SIZE(Address->ClientPart.AddressLen,
                                         Address->ClientPart.HWAddressLen);
        }
        BytesNeeded += Adapter->RegistryPath.Length;

        if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength >=
                BytesNeeded)
        {
            /* Write common info */
            Info = (PLAN_ADAPTER_INFO_S)Writing;
            Info->Index      = Adapter->Index;
            Info->Media      = Adapter->Media;
            Info->Speed      = Adapter->Speed;
            /* Ethernet specific XXX */
            Info->AddressLen = IEEE_802_ADDR_LENGTH;
            Info->Overhead   = Adapter->HeaderSize;
            Info->MTU        = Adapter->MTU;
            Info->RegKeySize = Adapter->RegistryPath.Length;

            /* Copy the name */
            Writing += sizeof(*Info);
            RtlCopyMemory(Adapter->RegistryPath.Buffer,
                          Writing,
                          Adapter->RegistryPath.Length);

            /* Write the address info */
            Writing += Adapter->RegistryPath.Length;

            for (ListEntry = Adapter->AddressList.Flink;
                 ListEntry != &Adapter->AddressList;
                 ListEntry = ListEntry->Flink)
            {
                Address = CONTAINING_RECORD(ListEntry, LAN_ADDRESS_C,
                                            ListEntry);
                AddrSize = LAN_ADDR_SIZE(Address->ClientPart.AddressLen,
                                         Address->ClientPart.HWAddressLen);
                RtlCopyMemory(Writing, &Address->ClientPart, AddrSize);
                Writing += AddrSize;
            }

            for (ListEntry = Adapter->ForeignList.Flink;
                 ListEntry != &Adapter->ForeignList;
                 ListEntry = ListEntry->Flink)
            {
                Address = CONTAINING_RECORD(ListEntry,
                                            LAN_ADDRESS_C,
                                            ListEntry);
                AddrSize = LAN_ADDR_SIZE(Address->ClientPart.AddressLen,
                                         Address->ClientPart.HWAddressLen);
                RtlCopyMemory(Writing, &Address->ClientPart, AddrSize);
                Writing += AddrSize;
            }

            ASSERT(BytesNeeded == Writing - Irp->AssociatedIrp.SystemBuffer);
        }
        else Status = STATUS_BUFFER_TOO_SMALL;
    }
    else Status = STATUS_NO_SUCH_DEVICE;

    KeReleaseSpinLock(&DeviceExt->Lock, OldIrql);

    LA_DbgPrint(MID_TRACE,("Ending (%d bytes)\n", BytesNeeded));

    Irp->IoStatus.Status = Status;
    Irp->IoStatus.Information = BytesNeeded;

    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return Status;
}

NTSTATUS
NTAPI
LanSetBufferedMode(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp,
    PIO_STACK_LOCATION IrpSp)
{
    PLAN_DEVICE_EXT DeviceExt = (PLAN_DEVICE_EXT)DeviceObject->DeviceExtension;
    PFILE_OBJECT FileObject = IrpSp->FileObject;
    PLAN_PROTOCOL Proto = FileObject->FsContext;
    NTSTATUS Status = STATUS_SUCCESS;
    KIRQL OldIrql;

    LA_DbgPrint(MID_TRACE,("Called %x\n", Proto));

    KeAcquireSpinLock(&DeviceExt->Lock, &OldIrql);

    if (IrpSp->Parameters.DeviceIoControl.InputBufferLength >=
            sizeof(Proto->Buffered))
        RtlCopyMemory(&Proto->Buffered,
                      Irp->AssociatedIrp.SystemBuffer,
                      sizeof(Proto->Buffered));
    else
        Status = STATUS_INVALID_PARAMETER;

    KeReleaseSpinLock(&DeviceExt->Lock, OldIrql);

    LA_DbgPrint(MID_TRACE,("Set buffered for %x to %d\n", Proto->Buffered));

    Status = Irp->IoStatus.Status = Status;
    Irp->IoStatus.Information = 0;

    IoCompleteRequest(Irp, IO_NETWORK_INCREMENT);
    return Status;
}

NTSTATUS
NTAPI
LanDispatch(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp)
{
    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
    NTSTATUS Status = STATUS_SUCCESS;

    LA_DbgPrint(MID_TRACE,("LanDispatch: %d\n", IrpSp->MajorFunction));
    if (IrpSp->MajorFunction != IRP_MJ_CREATE)
    {
        LA_DbgPrint(MID_TRACE,("FO %x, IrpSp->FO %x\n",
                               FileObject, IrpSp->FileObject));
        ASSERT(FileObject == IrpSp->FileObject);
    }

    switch (IrpSp->MajorFunction)
    {
        /* opening and closing handles to the device */
    case IRP_MJ_CREATE:
        /* Mostly borrowed from the named pipe file system */
        return LanCreateProtocol(DeviceObject, Irp, IrpSp);

    case IRP_MJ_CLOSE:
        /* Ditto the borrowing */
        return LanCloseProtocol(DeviceObject, Irp, IrpSp);

        /* write data */
    case IRP_MJ_WRITE:
        return LanWriteData(DeviceObject, Irp, IrpSp);

        /* read data */
    case IRP_MJ_READ:
        return LanReadData(DeviceObject, Irp, IrpSp);

    case IRP_MJ_DEVICE_CONTROL:
    {
        LA_DbgPrint(MID_TRACE,("DeviceIoControl: %x\n",
                               IrpSp->Parameters.DeviceIoControl.
                               IoControlCode));
        switch (IrpSp->Parameters.DeviceIoControl.IoControlCode)
        {
        case IOCTL_IF_ENUM_ADAPTERS:
            return LanEnumAdapters(DeviceObject, Irp, IrpSp);

        case IOCTL_IF_BUFFERED_MODE:
            return LanSetBufferedMode(DeviceObject, Irp, IrpSp);

        case IOCTL_IF_ADAPTER_INFO:
            return LanAdapterInfo(DeviceObject, Irp, IrpSp);

        default:
            Status = STATUS_NOT_IMPLEMENTED;
            Irp->IoStatus.Information = 0;
            LA_DbgPrint(MIN_TRACE, ("Unknown IOCTL (0x%x)\n",
                                    IrpSp->Parameters.DeviceIoControl.
                                    IoControlCode));
            break;
        }
        break;
    }

    /* unsupported operations */
    default:
        Status = STATUS_NOT_IMPLEMENTED;
        LA_DbgPrint(MIN_TRACE,
                    ("Irp: Unknown Major code was %x\n",
                     IrpSp->MajorFunction));
        break;
    }

    LA_DbgPrint(MID_TRACE, ("Returning %x\n", Status));
    Irp->IoStatus.Status = Status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return (Status);
}

/* Do i need a global here?  I think i need to do this a different way XXX */
VOID
NTAPI
LanUnload(
    PDRIVER_OBJECT DriverObject)
{
    LANUnregisterProtocol();
    CloseNdisPools();
}

NTSTATUS
NTAPI
DriverEntry(
    PDRIVER_OBJECT DriverObject,
    PUNICODE_STRING RegsitryPath)
{
    PDEVICE_OBJECT DeviceObject;
    PLAN_DEVICE_EXT DeviceExt;
    UNICODE_STRING wstrDeviceName = RTL_CONSTANT_STRING(L"\\Device\\Lan");
    UNICODE_STRING LanString = RTL_CONSTANT_STRING(L"LAN");
    NTSTATUS Status;

    InitNdisPools();

    /* register driver routines */
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = LanDispatch;
    DriverObject->MajorFunction[IRP_MJ_CREATE] = LanDispatch;
    DriverObject->MajorFunction[IRP_MJ_WRITE] = LanDispatch;
    DriverObject->MajorFunction[IRP_MJ_READ] = LanDispatch;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = LanDispatch;
    DriverObject->DriverUnload = LanUnload;

    /* create lan device */
    Status = IoCreateDevice(DriverObject,
                            sizeof(LAN_DEVICE_EXT),
                            &wstrDeviceName,
                            FILE_DEVICE_NAMED_PIPE,
                            0,
                            FALSE,
                            &DeviceObject);

    /* failure */
    if (!NT_SUCCESS(Status))
    {
        return (Status);
    }

    LanDeviceObject = DeviceObject;
    DeviceExt = DeviceObject->DeviceExtension;
    RtlZeroMemory(DeviceExt, sizeof(*DeviceExt));
    InitializeListHead(&DeviceExt->AdapterListHead);
    InitializeListHead(&DeviceExt->ProtocolListHead);
    KeInitializeSpinLock(&DeviceExt->Lock);

    LANRegisterProtocol(&LanString);

    DeviceObject->Flags |= DO_BUFFERED_IO;

    LA_DbgPrint(MID_TRACE,("Device created: object %x ext %x\n",
                           DeviceObject, DeviceExt));

    return (Status);
}

/* EOF */
